Normalization
We will start with the raw counts and Compute Q3 normalization.
## DGEList: Create object from counts, metadata and main variable of interest.
dge <- DGEList(counts = counts, samples = metadata)
## Q3 normalization
dge <- calcNormFactors(dge, method = 'upperquartile')
To obtain \(log_2CPM\) values, we
will use voom, in this case we use a simple design matrix, that captures
our main variables of interest and the batch variable.
## Initial design matrix: Include key variables that explain the variation in the study.
design = model.matrix(~CellType + class + slide_name, data = metadata)
logcpm <- voom(dge, design)$E
PCA
We will perform a PCA analysis to explore the variation in the
dataset, using logCPM values.



biplot(pca, pointSize = 2, colby = "slide_name", lab = NULL, legendPosition = 'right')
biplot(pca, x = 'PC3', y = 'PC4', pointSize = 2, colby = "slide_name", lab = NULL, legendPosition = 'right')
biplot(pca, pointSize = 2, colby = "CellType", shape = "class", lab = NULL, legendPosition = 'right')

biplot(pca, x = 'PC3', y = 'PC4', pointSize = 2, colby = "CellType", shape = "class", lab = NULL, legendPosition = 'right')

DE analysis: limma-voom
DE analysis performed using the limma package. edgeR,
limma-voom and DESeq2 are recommended for GeoMx data.
Strong preference for limma-voom using duplicate
correlation, as it is the most robust method for this type of data. Does
not modify the variation in the dataset and assumes a mixed effect for
the batch variable.
DESeq2 is recommended when you have raw counts, and you want to
include batch variables as covariates but you also have a full-rank
matrix (Slide is not confounded with the variable of interest).
In this case, we will start from the original dataset, since we will
use the duplicated correlation to account for the batch effect.
Design Model: ~ CellType + Class:CellType
## Block variable set to the batch variable
block_var = metadata$slide_name
## Design model.
design = model.matrix(~0 + CellType + class:CellType, data = metadata)
## Update contrasts names.
colnames(design) <- gsub("CellType|class", "", colnames(design)) %>% gsub(pattern = ':', rep = '_')
Biological Coefficient of Variation
keep <- filterByExpr(dge, design)
dge_all <- dge[keep, ]
dge_all <- estimateDisp(dge_all, design = design, robust = TRUE)
plotBCV(dge_all, ylim = c(0, 1.3))
bcv_df <- data.frame(
'BCV' = sqrt(dge_all$tagwise.dispersion),
'AveLogCPM' = dge_all$AveLogCPM,
'gene_id' = rownames(dge_all)
)
highbcv <- bcv_df$BCV > 0.8
highbcv_df <- bcv_df[highbcv, ]
points(highbcv_df$AveLogCPM, highbcv_df$BCV, col = "red")
text(highbcv_df$AveLogCPM, highbcv_df$BCV, labels = highbcv_df$gene_id, pos = 4)

Fit model
# Estimate correlation within slides
corfit <- duplicateCorrelation(voom(dge, design), block = block_var)
# Run voom with duplicate correlation
v <- voom(dge, design, block = block_var, correlation = corfit$consensus, plot = T)

# Fit the model
fit <- lmFit(v, design, block = block_var, correlation = corfit$consensus)
fit <- eBayes(fit)
We also check the value of the concensus correlation, part of the
output of the duplicateCorrelation function.
If the value is < 0.1, blocking might not as needed. If the value
is >0.5, you might need a second round of duplicateCorrelation, same
code, but using the residuals of the first model.
In this case, corfit$consensus.correlation is
0.205, so the batch seems to have an observable
effect.
colnames( fit$coefficients )
[1] "glomeruli" "ProximalTubules" "DistalTubules" "glomeruli_DKD" "ProximalTubules_DKD"
[6] "DistalTubules_DKD"
Results
In this case we will focus on DE between disease and normal samples
in glomeruli, which is represented by the glomeruli_DKD
coefficient.
Top 20 DEGs DKD vs Normal, glomeruli
| TSPY1 |
1.707835 |
0.0000002 |
0.0000373 |
| SUPT7L |
-1.528025 |
0.0000000 |
0.0000001 |
| IL1RL1 |
-1.484195 |
0.0000090 |
0.0009137 |
| NELL1 |
1.473469 |
0.0000013 |
0.0002036 |
| DEFA1B |
1.450012 |
0.0006777 |
0.0230282 |
| CDKN1C |
-1.416080 |
0.0000000 |
0.0000000 |
| SLC2A3 |
1.376519 |
0.0000002 |
0.0000410 |
| NDNF |
-1.332004 |
0.0000000 |
0.0000000 |
| RBMY1J |
1.322612 |
0.0000003 |
0.0000522 |
| RPS4Y1 |
1.310669 |
0.0001131 |
0.0065576 |
| PCOLCE2 |
-1.273577 |
0.0000000 |
0.0000000 |
| PRY |
1.255908 |
0.0000807 |
0.0049619 |
| INMT |
-1.252914 |
0.0000001 |
0.0000324 |
| DCN |
-1.231292 |
0.0000001 |
0.0000274 |
| ESM1 |
-1.194987 |
0.0000099 |
0.0009685 |
| MME |
-1.190490 |
0.0000000 |
0.0000006 |
| EPAS1 |
-1.181506 |
0.0000000 |
0.0000051 |
| S100A9 |
1.179379 |
0.0017487 |
0.0446220 |
| HIPK2 |
-1.171479 |
0.0000000 |
0.0000000 |
| S100A8 |
1.152229 |
0.0004539 |
0.0177107 |

Heatmaps
For visualization, we can use one of the batch corrected datasets, in
this case, we will use the limma batch corrected dataset.
We will plot the logNorm expression in the Top 20 DEG in
Glomeruli.
## Heatmaps: Top 20 DEGs.
top_20 = filter(de, adj.P.Val < 0.05 & abs(logFC) >= log2(1.5)) %>%
dplyr::select(Gene, logFC, P.Value, adj.P.Val) %>%
arrange(desc(abs(logFC))) %>% head(n = 20) %>% pull('Gene')
norm_mx = logcpm[top_20, metadata$CellType == i]
scale(norm_mx) -> norm_mx
ComplexHeatmap::Heatmap(logcpm[top_20, metadata$CellType == i],
name = 'logCPM', col = c('white', 'red3'),
column_split = metadata$class[metadata$CellType == i],
cluster_rows = F, cluster_columns = F, show_row_names = T, show_column_names = F)

norm_mx = logcpm[top_20, metadata$CellType == i] %>% t %>% scale()
ComplexHeatmap::Heatmap(t(norm_mx),
name = 'z-score',
column_split = metadata$class[metadata$CellType == i],
cluster_rows = F, cluster_columns = F, show_row_names = T, show_column_names = F)

LS0tCnRpdGxlOiAnREUgYW5hbHlzaXMgdXNpbmcgbGltbWE6IE5vcm1hbGl6YXRpb24sIEJhdGNoIGNvcnJlY3Rpb24gYW5kIERFIGFuYWx5c2lzJwphdXRob3I6ICJEaWFuYSBWZXJhIENydXoiCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQpzdWJ0aXRsZTogS2lkbmV5IGV4YW1wbGUgZGF0YXNldAotLS0KCiMjIEdvYWwKClBlcmZvcm0gbm9ybWFsaXphdGlvbiBhbmQgREUgYW5hbHlzaXMgdXNpbmcgdGhlIGxpbW1hIHBhY2thZ2UuIFdlIHJlY29tbWVuZCB0byB1c2UgdGhlIGBsaW1tYS12b29tYCBtZXRob2QgZm9yIERFIGFuYWx5c2lzLCBhcyBpdCBpcyB0aGUgbW9zdCByb2J1c3QgbWV0aG9kIGZvciB0aGlzIHR5cGUgb2YgZGF0YSwgY291cGxlZCB3aXRoIHRoZSBgZHVwbGljYXRlQ29ycmVsYXRpb25gIGZ1bmN0aW9uIHRvIGFjY291bnQgZm9yIHRoZSBiYXRjaCBlZmZlY3QuIAoKIyMgREUgcXVlc3Rpb24gCgoqKkZvciBhIGdpdmVuIGNlbGwgdHlwZSwgZG8gd2Ugc2VlIGRpZmZlcmVuY2VzIGJldHdlZW4gdGhlIG5vcm1hbCBhbmQgREtEIHNlZ21lbnRzPyoqCgoKIyMgSW5wdXQgRGF0YQoKV2Ugd2lsbCB1c2UgdGhlIHRhYmxlcyBnZW5lcmF0ZWQgaW4gdGhlIHByZXZpb3VzIFFDIHN0ZXAsIHRoZXkgYXJlIGZvdW5kIGluIHRoZSByZXN1bHRzIGZvbGRlci4KCi0gICAqKkNvdW50cyoqOiBEYXRhZnJhbWUsIG9yIHRhYmxlLCBpbmNsdWRpbmcgYSBjb2x1bW4gd2l0aCB0aGUgZ2VuZSBuYW1lIChEZWZhdWx0LCBgVGFyZ2V0TmFtZWApLgoKLSAgICoqU2FtcGxlIEFubm90YXRpb24qKjogU2FtcGxlIGFubm90YXRpb24sIGl0IHNob3VsZCBpbmNsdWRlIGEgc2VnbWVudCBuYW1lIGNvbHVtbiwgKERlZmF1bHQsIGBTZWdtZW50RGlzcGxheU5hbWVgKSBhbmQgQ29vcmRpbmF0ZSBjb2x1bW5zIChEZWZhdWx0LCBgUk9JQ29vcmRpbmF0ZVhgIGFuZCBgUk9JQ29vcmRpbmF0ZVlgKS4KCgoKYGBge3Igc2V0dXAsIGluY2x1ZGUgPSBGfQojIyBMaWJyYXJpZXMKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobGltbWEpCmxpYnJhcnkoZWRnZVIpCmxpYnJhcnkoZ2dhbGx1dmlhbCkKbGlicmFyeShnZ3JlcGVsKQpsaWJyYXJ5KFBDQXRvb2xzKQpgYGAKCmBgYHtyIHN0dWR5X2RhdGEsIG1lc3NhZ2UgPSBGfQpjb3VudHMgPSByZWFkX3RzdignLi4vcmVzdWx0cy90aWR5X2NvdW50cy50c3YnKSAlPiUgY29sdW1uX3RvX3Jvd25hbWVzKCdUYXJnZXROYW1lJykgJT4lIGFzLm1hdHJpeCgpCgojIyBNZXRhZGF0YTogRm9ybWF0IGFuZCBlbnN1cmUgdGhhdCB0aGUgdmFyaWFibGVzIG9mIGludGVyZXN0IGFyZSBmYWN0b3JzLCBhbmQgaW4gdGhlIGRlc2lyZWQgb3JkZXIuCm1ldGFkYXRhID0gcmVhZF90c3YoJy4uL3Jlc3VsdHMvdGlkeV9tZXRhZGF0YS50c3YnKSAlPiUgYXMuZGF0YS5mcmFtZSgpCnJvd25hbWVzKG1ldGFkYXRhKSA9IG1ldGFkYXRhJHNlZ21lbnRfbmFtZQptZXRhZGF0YSA8LSBtdXRhdGUobWV0YWRhdGEsIAogICAgICAgQ2VsbFR5cGUgPSBjYXNlX3doZW4oc2VnbWVudCA9PSAnR2VvbWV0cmljIFNlZ21lbnQnIH4gJ2dsb21lcnVsaScsIHNlZ21lbnQgPT0gJ1BhbkNLLScgfiAnRGlzdGFsVHVidWxlcycsIFRSVUUgfiAnUHJveGltYWxUdWJ1bGVzJykgJT4lIGZhY3RvcihsZXZlbHMgPSBjKCdnbG9tZXJ1bGknLCAnUHJveGltYWxUdWJ1bGVzJywgJ0Rpc3RhbFR1YnVsZXMnKSksIAogICAgICAgY2xhc3MgPSBmYWN0b3IoY2xhc3MsIGxldmVscyA9IGMoJ25vcm1hbCcsICdES0QnKSkKICAgICAgICkKCnN0cihtZXRhZGF0YSkKYGBgCgojIyBEYXRhc2V0IGV4cGxvcmF0aW9uCgoKYGBge3J9CmRwbHlyOjpjb3VudChtZXRhZGF0YSwgc2xpZGVfbmFtZSwgQ2VsbFR5cGUsIHNlZ21lbnQsIHJlZ2lvbiwgY2xhc3MpCmBgYAoKCiMjIE5vcm1hbGl6YXRpb24KCldlIHdpbGwgc3RhcnQgd2l0aCB0aGUgcmF3IGNvdW50cyBhbmQgQ29tcHV0ZSBRMyBub3JtYWxpemF0aW9uLgoKYGBge3J9CiMjIERHRUxpc3Q6IENyZWF0ZSBvYmplY3QgZnJvbSBjb3VudHMsIG1ldGFkYXRhIGFuZCBtYWluIHZhcmlhYmxlIG9mIGludGVyZXN0LgpkZ2UgPC0gREdFTGlzdChjb3VudHMgPSBjb3VudHMsIHNhbXBsZXMgPSBtZXRhZGF0YSkKCiMjIFEzIG5vcm1hbGl6YXRpb24KZGdlIDwtIGNhbGNOb3JtRmFjdG9ycyhkZ2UsIG1ldGhvZCA9ICd1cHBlcnF1YXJ0aWxlJykKYGBgCgpUbyBvYnRhaW4gJGxvZ18yQ1BNJCB2YWx1ZXMsIHdlIHdpbGwgdXNlIHZvb20sIGluIHRoaXMgY2FzZSB3ZSB1c2UgYSBzaW1wbGUgZGVzaWduIG1hdHJpeCwgdGhhdCBjYXB0dXJlcyBvdXIgbWFpbiB2YXJpYWJsZXMgb2YgaW50ZXJlc3QgYW5kIHRoZSBiYXRjaCB2YXJpYWJsZS4KCmBgYHtyfQojIyBJbml0aWFsIGRlc2lnbiBtYXRyaXg6IEluY2x1ZGUga2V5IHZhcmlhYmxlcyB0aGF0IGV4cGxhaW4gdGhlIHZhcmlhdGlvbiBpbiB0aGUgc3R1ZHkuIApkZXNpZ24gPSBtb2RlbC5tYXRyaXgofkNlbGxUeXBlICsgY2xhc3MgKyBzbGlkZV9uYW1lLCBkYXRhID0gbWV0YWRhdGEpCmxvZ2NwbSA8LSB2b29tKGRnZSwgZGVzaWduKSRFCmBgYAoKCgojIyMgUENBCgpXZSB3aWxsIHBlcmZvcm0gYSBQQ0EgYW5hbHlzaXMgdG8gZXhwbG9yZSB0aGUgdmFyaWF0aW9uIGluIHRoZSBkYXRhc2V0LCB1c2luZyBsb2dDUE0gdmFsdWVzLiAKCmBgYHtyfQpwY2EgPC0gcGNhKGxvZ2NwbSwgbWV0YSA9IG1ldGFkYXRhLCBzY2FsZSA9IFQpCgpzY3JlZXBsb3QocGNhLCBjb21wb25lbnRzID0gMTo1MCwgYXhpc0xhYlNpemUgPSAxMikKYGBgCgoKYGBge3J9CmJpcGxvdChwY2EsIHBvaW50U2l6ZSA9IDIsIGNvbGJ5ID0gInNsaWRlX25hbWUiLCBsYWIgPSBOVUxMLCBsZWdlbmRQb3NpdGlvbiA9ICdyaWdodCcpCmJpcGxvdChwY2EsIHggPSAnUEMzJywgeSA9ICdQQzQnLCBwb2ludFNpemUgPSAyLCBjb2xieSA9ICJzbGlkZV9uYW1lIiwgbGFiID0gTlVMTCwgbGVnZW5kUG9zaXRpb24gPSAncmlnaHQnKQoKYmlwbG90KHBjYSwgcG9pbnRTaXplID0gMiwgY29sYnkgPSAiQ2VsbFR5cGUiLCBzaGFwZSA9ICJjbGFzcyIsIGxhYiA9IE5VTEwsIGxlZ2VuZFBvc2l0aW9uID0gJ3JpZ2h0JykKYmlwbG90KHBjYSwgeCA9ICdQQzMnLCB5ID0gJ1BDNCcsIHBvaW50U2l6ZSA9IDIsIGNvbGJ5ID0gIkNlbGxUeXBlIiwgc2hhcGUgPSAiY2xhc3MiLCBsYWIgPSBOVUxMLCBsZWdlbmRQb3NpdGlvbiA9ICdyaWdodCcpCmBgYAoKCiMjIERFIGFuYWx5c2lzOiBgbGltbWEtdm9vbWAKCkRFIGFuYWx5c2lzIHBlcmZvcm1lZCB1c2luZyB0aGUgYGxpbW1hYCBwYWNrYWdlLiBlZGdlUiwgbGltbWEtdm9vbSBhbmQgREVTZXEyIGFyZSByZWNvbW1lbmRlZCBmb3IgR2VvTXggZGF0YS4KClN0cm9uZyBwcmVmZXJlbmNlIGZvciBgbGltbWEtdm9vbWAgdXNpbmcgZHVwbGljYXRlIGNvcnJlbGF0aW9uLCBhcyBpdCBpcyB0aGUgbW9zdCByb2J1c3QgbWV0aG9kIGZvciB0aGlzIHR5cGUgb2YgZGF0YS4gRG9lcyBub3QgbW9kaWZ5IHRoZSB2YXJpYXRpb24gaW4gdGhlIGRhdGFzZXQgYW5kIGFzc3VtZXMgYSBtaXhlZCBlZmZlY3QgZm9yIHRoZSBiYXRjaCB2YXJpYWJsZS4KCkRFU2VxMiBpcyByZWNvbW1lbmRlZCB3aGVuIHlvdSBoYXZlIHJhdyBjb3VudHMsIGFuZCB5b3Ugd2FudCB0byBpbmNsdWRlIGJhdGNoIHZhcmlhYmxlcyBhcyBjb3ZhcmlhdGVzIGJ1dCB5b3UgYWxzbyBoYXZlIGEgZnVsbC1yYW5rIG1hdHJpeCAoU2xpZGUgaXMgbm90IGNvbmZvdW5kZWQgd2l0aCB0aGUgdmFyaWFibGUgb2YgaW50ZXJlc3QpLgoKSW4gdGhpcyBjYXNlLCB3ZSB3aWxsIHN0YXJ0IGZyb20gdGhlIG9yaWdpbmFsIGRhdGFzZXQsIHNpbmNlIHdlIHdpbGwgdXNlIHRoZSBkdXBsaWNhdGVkIGNvcnJlbGF0aW9uIHRvIGFjY291bnQgZm9yIHRoZSBiYXRjaCBlZmZlY3QuCgoqKkRlc2lnbiBNb2RlbCoqOiBcfiBDZWxsVHlwZSArIENsYXNzOkNlbGxUeXBlCgoKYGBge3J9CiMjIEJsb2NrIHZhcmlhYmxlIHNldCB0byB0aGUgYmF0Y2ggdmFyaWFibGUKYmxvY2tfdmFyID0gbWV0YWRhdGEkc2xpZGVfbmFtZQoKIyMgRGVzaWduIG1vZGVsLiAKZGVzaWduID0gbW9kZWwubWF0cml4KH4wICsgQ2VsbFR5cGUgKyBjbGFzczpDZWxsVHlwZSwgZGF0YSA9IG1ldGFkYXRhKQojIyBVcGRhdGUgY29udHJhc3RzIG5hbWVzLiAKY29sbmFtZXMoZGVzaWduKSA8LSBnc3ViKCJDZWxsVHlwZXxjbGFzcyIsICIiLCBjb2xuYW1lcyhkZXNpZ24pKSAlPiUgZ3N1YihwYXR0ZXJuID0gJzonLCByZXAgPSAnXycpCmBgYAoKIyMjIyBCaW9sb2dpY2FsIENvZWZmaWNpZW50IG9mIFZhcmlhdGlvbgoKYGBge3IsIHdhcm5pbmcgPSBGfQprZWVwIDwtIGZpbHRlckJ5RXhwcihkZ2UsIGRlc2lnbikKZGdlX2FsbCA8LSBkZ2Vba2VlcCwgXQpkZ2VfYWxsIDwtIGVzdGltYXRlRGlzcChkZ2VfYWxsLCBkZXNpZ24gPSBkZXNpZ24sIHJvYnVzdCA9IFRSVUUpCgpwbG90QkNWKGRnZV9hbGwsIHlsaW0gPSBjKDAsIDEuMykpCmJjdl9kZiA8LSBkYXRhLmZyYW1lKAogICdCQ1YnID0gc3FydChkZ2VfYWxsJHRhZ3dpc2UuZGlzcGVyc2lvbiksCiAgJ0F2ZUxvZ0NQTScgPSBkZ2VfYWxsJEF2ZUxvZ0NQTSwKICAnZ2VuZV9pZCcgPSByb3duYW1lcyhkZ2VfYWxsKQopCgpoaWdoYmN2IDwtIGJjdl9kZiRCQ1YgPiAwLjgKaGlnaGJjdl9kZiA8LSBiY3ZfZGZbaGlnaGJjdiwgXQpwb2ludHMoaGlnaGJjdl9kZiRBdmVMb2dDUE0sIGhpZ2hiY3ZfZGYkQkNWLCBjb2wgPSAicmVkIikKdGV4dChoaWdoYmN2X2RmJEF2ZUxvZ0NQTSwgaGlnaGJjdl9kZiRCQ1YsIGxhYmVscyA9IGhpZ2hiY3ZfZGYkZ2VuZV9pZCwgcG9zID0gNCkKYGBgCgojIyMjIEZpdCBtb2RlbAoKYGBge3J9CiMgRXN0aW1hdGUgY29ycmVsYXRpb24gd2l0aGluIHNsaWRlcwpjb3JmaXQgPC0gZHVwbGljYXRlQ29ycmVsYXRpb24odm9vbShkZ2UsIGRlc2lnbiksIGJsb2NrID0gYmxvY2tfdmFyKQoKIyBSdW4gdm9vbSB3aXRoIGR1cGxpY2F0ZSBjb3JyZWxhdGlvbgp2IDwtIHZvb20oZGdlLCBkZXNpZ24sIGJsb2NrID0gYmxvY2tfdmFyLCBjb3JyZWxhdGlvbiA9IGNvcmZpdCRjb25zZW5zdXMsIHBsb3QgPSBUKQoKIyBGaXQgdGhlIG1vZGVsCmZpdCA8LSBsbUZpdCh2LCBkZXNpZ24sIGJsb2NrID0gYmxvY2tfdmFyLCBjb3JyZWxhdGlvbiA9IGNvcmZpdCRjb25zZW5zdXMpCmZpdCA8LSBlQmF5ZXMoZml0KQpgYGAKCldlIGFsc28gY2hlY2sgdGhlIHZhbHVlIG9mIHRoZSBjb25jZW5zdXMgY29ycmVsYXRpb24sIHBhcnQgb2YgdGhlIG91dHB1dCBvZiB0aGUgYGR1cGxpY2F0ZUNvcnJlbGF0aW9uYCBmdW5jdGlvbi4KCklmIHRoZSB2YWx1ZSBpcyA8IDAuMSwgYmxvY2tpbmcgbWlnaHQgbm90IGFzIG5lZWRlZC4gCklmIHRoZSB2YWx1ZSBpcyA+MC41LCB5b3UgbWlnaHQgbmVlZCBhIHNlY29uZCByb3VuZCBvZiBkdXBsaWNhdGVDb3JyZWxhdGlvbiwgc2FtZSBjb2RlLCBidXQgdXNpbmcgdGhlIHJlc2lkdWFscyBvZiB0aGUgZmlyc3QgbW9kZWwuIAoKSW4gdGhpcyBjYXNlLCBgY29yZml0JGNvbnNlbnN1cy5jb3JyZWxhdGlvbmAgaXMgKipgciByb3VuZChjb3JmaXQkY29uc2Vuc3VzLmNvcnJlbGF0aW9uLCBkaWdpdHMgPSAzKWAqKiwgc28gdGhlIGJhdGNoIHNlZW1zIHRvIGhhdmUgYW4gb2JzZXJ2YWJsZSBlZmZlY3QuIAoKYGBge3J9CiMgRXh0cmFjdCByZXN1bHRzIGZvciBhIHNwZWNpZmljIGNsYXNzIGVmZmVjdCB3aXRoaW4gYSBjZWxsIHR5cGUuCmNvbG5hbWVzKCBmaXQkY29lZmZpY2llbnRzICkKYGBgCgojIyMjIFJlc3VsdHMKCkluIHRoaXMgY2FzZSB3ZSB3aWxsIGZvY3VzIG9uIERFIGJldHdlZW4gZGlzZWFzZSBhbmQgbm9ybWFsIHNhbXBsZXMgaW4gZ2xvbWVydWxpLCB3aGljaCBpcyByZXByZXNlbnRlZCBieSB0aGUgYGdsb21lcnVsaV9ES0RgIGNvZWZmaWNpZW50LgoKYGBge3IsIHdhcm5pbmdzID0gRn0KdGhpc19jb2VmID0gJ2dsb21lcnVsaV9ES0QnCmkgPSAnZ2xvbWVydWxpJwoKIyMgRXh0cmFjdCBhbGwgdGhlIHJlc3VsdHMgcGVyIGdlbmU6IE5vIGZpbHRlciBmb3IgcC12YWx1ZSBub3IgbG9nRkMsIHdpdGggcmVndWxhciBhZGp1c3RtZW50LgpkZSA8LSB0b3BUYWJsZShmaXQsIGNvZWY9dGhpc19jb2VmLCAgbnVtYmVyPUluZikgJT4lIG11dGF0ZShDZWxsVHlwZSA9IGkpICU+JSByb3duYW1lc190b19jb2x1bW4oJ0dlbmUnKQogIAojIyBTaG93IERFLiAKZmlsdGVyKGRlLCBhZGouUC5WYWwgPCAwLjA1ICYgYWJzKGxvZ0ZDKSA+PSBsb2cyKDEuNSkpICU+JSAKICAgIGRwbHlyOjpzZWxlY3QoR2VuZSwgbG9nRkMsIFAuVmFsdWUsIGFkai5QLlZhbCkgJT4lIAogICAgYXJyYW5nZShkZXNjKGFicyhsb2dGQykpKSAlPiUgaGVhZChuID0gMjApICU+JSAKICAgIGtuaXRyOjprYWJsZShjYXB0aW9uID0gcGFzdGUwKCdUb3AgMjAgREVHc1xuREtEIHZzIE5vcm1hbCwgJywgaSkpCmBgYAoKCmBgYHtyLCB3YXJuaW5ncyA9IEZ9CiMjIFZvbGNhbm8gcGxvdApkZSAlPiUgbXV0YXRlKHNpZyA9IGFkai5QLlZhbCA8IDAuMDUgJiBhYnMobG9nRkMpID49IGxvZzIoMS41KSwgZ2VuZSA9IGlmZWxzZShzaWcgPT0gVCwgR2VuZSwgJycpKSAlPiUgCiAgICBnZ3Bsb3QoYWVzKHggPSBsb2dGQywgeSA9IC1sb2cxMChhZGouUC5WYWwpLCBjb2xvciA9IHNpZywgbGFiZWwgPSBnZW5lKSkgKyB0aGVtZV9idygpICsKICAgIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDEuMywgY29sb3IgPSAnb3JhbmdlJywgbGluZXR5cGUgPSAnZGFzaGVkJykgKyAKICAgIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoLTAuNTksIDAuNTkpLCAgY29sb3IgPSAnZ3JleTUwJywgbGluZXR5cGUgPSAnZGFzaGVkJykgKwogICAgZ2VvbV9wb2ludChzaXplID0gMC41KSArIGdlb21fdGV4dF9yZXBlbChzaXplID0gMywgY29sb3IgPSAnYmxhY2snLCBtYXgub3ZlcmxhcHMgPSAxNSkgKyAKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSBjKGBGQUxTRWAgPSAnYmxhY2snLCBgVFJVRWAgPSAncmVkMycpKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpICsgbGFicyh0aXRsZSA9IHBhc3RlKCdES0QgdnMgTm9ybWFsJywgaSksIHkgPSAnLWxvZzEwKEFkai4gUC12YWx1ZSknLCB4ID0gJ2xvZzJGQycpCmBgYAoKIyMjIyBIZWF0bWFwcwoKRm9yIHZpc3VhbGl6YXRpb24sIHdlIGNhbiB1c2Ugb25lIG9mIHRoZSBiYXRjaCBjb3JyZWN0ZWQgZGF0YXNldHMsIGluIHRoaXMgY2FzZSwgd2Ugd2lsbCB1c2UgdGhlIGxpbW1hIGJhdGNoIGNvcnJlY3RlZCBkYXRhc2V0LgoKV2Ugd2lsbCBwbG90IHRoZSBsb2dOb3JtIGV4cHJlc3Npb24gaW4gdGhlIFRvcCAyMCBERUcgaW4gR2xvbWVydWxpLgoKYGBge3J9CiMjIEhlYXRtYXBzOiBUb3AgMjAgREVHcy4gCnRvcF8yMCA9IGZpbHRlcihkZSwgYWRqLlAuVmFsIDwgMC4wNSAmIGFicyhsb2dGQykgPj0gbG9nMigxLjUpKSAlPiUgCiAgICBkcGx5cjo6c2VsZWN0KEdlbmUsIGxvZ0ZDLCBQLlZhbHVlLCBhZGouUC5WYWwpICU+JSAKICAgIGFycmFuZ2UoZGVzYyhhYnMobG9nRkMpKSkgJT4lIGhlYWQobiA9IDIwKSAlPiUgcHVsbCgnR2VuZScpCgpub3JtX214ID0gbG9nY3BtW3RvcF8yMCwgbWV0YWRhdGEkQ2VsbFR5cGUgPT0gaV0Kc2NhbGUobm9ybV9teCkgLT4gbm9ybV9teAoKCkNvbXBsZXhIZWF0bWFwOjpIZWF0bWFwKGxvZ2NwbVt0b3BfMjAsIG1ldGFkYXRhJENlbGxUeXBlID09IGldLCAKICAgICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICdsb2dDUE0nLCBjb2wgPSBjKCd3aGl0ZScsICdyZWQzJyksCiAgICAgICAgICAgICAgICAgICAgICAgIGNvbHVtbl9zcGxpdCA9IG1ldGFkYXRhJGNsYXNzW21ldGFkYXRhJENlbGxUeXBlID09IGldLAogICAgICAgICAgICAgICAgICAgICAgICBjbHVzdGVyX3Jvd3MgPSBGLCBjbHVzdGVyX2NvbHVtbnMgPSBGLCBzaG93X3Jvd19uYW1lcyA9IFQsIHNob3dfY29sdW1uX25hbWVzID0gRikKCm5vcm1fbXggPSBsb2djcG1bdG9wXzIwLCBtZXRhZGF0YSRDZWxsVHlwZSA9PSBpXSAlPiUgdCAlPiUgc2NhbGUoKQoKQ29tcGxleEhlYXRtYXA6OkhlYXRtYXAodChub3JtX214KSwgCiAgICAgICAgICAgICAgICAgICAgICAgIG5hbWUgPSAnei1zY29yZScsIAogICAgICAgICAgICAgICAgICAgICAgICBjb2x1bW5fc3BsaXQgPSBtZXRhZGF0YSRjbGFzc1ttZXRhZGF0YSRDZWxsVHlwZSA9PSBpXSwKICAgICAgICAgICAgICAgICAgICAgICAgY2x1c3Rlcl9yb3dzID0gRiwgY2x1c3Rlcl9jb2x1bW5zID0gRiwgc2hvd19yb3dfbmFtZXMgPSBULCBzaG93X2NvbHVtbl9uYW1lcyA9IEYpCgpgYGAKCiMjIFIgU2Vzc2lvbgoKYGBge3J9Cm1hcChzZXNzaW9uSW5mbygpJG90aGVyUGtncywgfi54JFZlcnNpb24pCmBgYAo=